﻿using System;
using System.IO;

namespace tuimg
{
    using tuimg.element;
    using tuimg.helpers;
    using tuimg.structs;

    public class TuimgBmp : IDisposable
    {
        internal const ushort bmp_tag = 0x4d42;
        // dib头  40-bytes
        internal struct_bmp_infohead infohead;
        // 14-bytes
        internal struct_bmp_head head;
        internal TuimgColor[] colors;
        internal bool has_color_palette = false;
        internal uint start_colors;
        internal int padding_size;
        internal int biWidth_plus1;
        internal byte onepoint_size;
        internal MemoryStream img_data;
        internal struct_bmp_head read_head(Stream sr)
        {
            var head = new struct_bmp_head { };

            head.bfType = helper.Readushort(sr);
            if (head.bfType != bmp_tag) throw new ArgumentException("not bmp file.");
            head.bfSize = helper.Readuint(sr);
            head.bfReserved1 = helper.Readushort(sr);
            head.bfReserved2 = helper.Readushort(sr);
            head.bfOffBits = helper.Readuint(sr);
            return head;
        }
        internal static struct_bmp_infohead read_info_head(Stream sr)
        {
            var infohead = new struct_bmp_infohead { };

            infohead.biSize = helper.Readuint(sr);
            infohead.biWidth = helper.Readint(sr);
            infohead.biHeight = helper.Readint(sr);
            infohead.biHeight_minus1 = infohead.biHeight - 1;
            infohead.biPlanes = helper.Readushort(sr);
            infohead.biBitCount = helper.Readushort(sr);
            infohead.biCompression = helper.Readuint(sr);
            infohead.biSizeImage = helper.Readuint(sr);
            infohead.biXPelsPerMeter = helper.Readint(sr);
            infohead.biYPelsPerMeter = helper.Readint(sr);
            infohead.biClrUsed = helper.Readuint(sr);
            infohead.biClrImportant = helper.Readuint(sr);

            return infohead;
        }
        internal void calc_info_head()
        {
            onepoint_size = (byte)(infohead.biBitCount>>3);
            start_colors = 14 + infohead.biSize;
            padding_size = calc_padding();
            biWidth_plus1 = onepoint_size * infohead.biWidth + padding_size;
        }
        internal TuimgColor[] read_colors(Stream sr)
        {
            var colors = new TuimgColor[calc_colors()];
            if (colors.Length > 0) for (var i = 0; i < colors.Length; i++) colors[i] = helper.ReadColor32Bgr_reserved(sr);
            if (colors.Length > 0) has_color_palette = true;
            return colors;
        }
        public TuimgBmp(string path)
        {
            img_data = new MemoryStream();
            using (var fr = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                head = read_head(fr);
                infohead = read_info_head(fr);
                calc_info_head();
                colors = read_colors(fr);

                helper.ReadToMemory(fr, img_data, calc_imgdata_size());
            }
        }
        public TuimgBmp(Stream s, bool has_and = false)
        {
            img_data = new MemoryStream();

            if (helper.read_image_type(s) == "bmp") head = read_head(s);
            else
            {
                head = new struct_bmp_head { };
                head.bfType = bmp_tag;
                head.bfSize = 0;
                head.bfReserved1 = 0;
                head.bfReserved2 = 0;
                head.bfOffBits = 54;
            }
            infohead = read_info_head(s);
            calc_info_head();
            colors = read_colors(s);

            if (has_and)
            {
                infohead.biHeight >>= 1;
                infohead.biHeight_minus1 = infohead.biHeight - 1;
                var img_size = calc_imgdata_size();
                helper.ReadToMemory(s, img_data, img_size);
            }
            else
            {
                helper.ReadToMemory(s, img_data, calc_imgdata_size());
            }
        }

        public TuimgBmp(int width, int height, ushort bitcount = 24)
        {
            infohead = new struct_bmp_infohead { };
            head = new struct_bmp_head { };
            onepoint_size = (byte)(bitcount>>3);
            head.bfType = bmp_tag;
            head.bfSize = 0;
            head.bfReserved1 = 0;
            head.bfReserved2 = 0;
            head.bfOffBits = 54;

            infohead.biSize = 40;
            infohead.biWidth = width;
            infohead.biHeight = height;
            infohead.biHeight_minus1 = infohead.biHeight - 1;
            infohead.biPlanes = 1;
            infohead.biBitCount = bitcount;
            infohead.biCompression = 0;
            infohead.biSizeImage = 0;
            infohead.biXPelsPerMeter = 0;
            infohead.biYPelsPerMeter = 0;
            infohead.biClrUsed = 0;
            infohead.biClrImportant = 0;

            start_colors = 14 + infohead.biSize;
            padding_size = calc_padding();
            infohead.biSizeImage = calc_imgdata_size();
            head.bfSize = calc_filesize();
            biWidth_plus1 = onepoint_size * infohead.biWidth + padding_size;

            img_data = new MemoryStream();
            var color = new TuimgColor(0xff,0xff,0xff);
            for (int irow = 0; irow < height; irow++) {
                for (int icol = 0; icol < width; icol++) helper.WriteColor(img_data, color.ToBitColor(bitcount));
                for (int ipadding = 0; ipadding < padding_size; ipadding++) img_data.WriteByte(0);
            }
        }
        public long Height { get { return infohead.biHeight; } }
        public long Width { get { return infohead.biWidth; } }

        public Stream ToStream()
        {
            var mem = new MemoryStream();
            ToStream(mem);
            return mem;
        }
        public void ToStream(Stream mem)
        {
            infohead.biSizeImage = calc_imgdata_size();
            head.bfSize = calc_filesize();

            helper.Write(mem, head.bfType);
            helper.Write(mem, head.bfSize);
            helper.Write(mem, head.bfReserved1);
            helper.Write(mem, head.bfReserved2);
            helper.Write(mem, head.bfOffBits);
            
            helper.Write(mem, infohead.biSize);
            helper.Write(mem, infohead.biWidth );
            helper.Write(mem, infohead.biHeight );
            helper.Write(mem, infohead.biPlanes );
            helper.Write(mem, infohead.biBitCount );
            helper.Write(mem, infohead.biCompression );
            helper.Write(mem, infohead.biSizeImage );
            helper.Write(mem, infohead.biXPelsPerMeter );
            helper.Write(mem, infohead.biYPelsPerMeter );
            helper.Write(mem, infohead.biClrUsed );
            helper.Write(mem, infohead.biClrImportant);

            var len_colors = calc_colors();
            if (len_colors > 0) for (var i = 0; i < len_colors; i++) helper.WriteColor(mem, colors[i].ToBitColor(32));

            img_data.Position = 0;
            helper.ReadToMemory(img_data, mem, infohead.biSizeImage);
            mem.Flush();
            mem.Seek(0, SeekOrigin.Begin);
        }
        public Stream ToIconStream(TuimgGrid and)
        {
            var mem = new MemoryStream();

            infohead.biSizeImage = calc_imgdata_size();
            head.bfSize = calc_filesize();

            helper.Write(mem, infohead.biSize);
            helper.Write(mem, infohead.biWidth);
            helper.Write(mem, infohead.biHeight<<1);
            helper.Write(mem, infohead.biPlanes);
            helper.Write(mem, infohead.biBitCount);
            helper.Write(mem, infohead.biCompression);
            helper.Write(mem, infohead.biSizeImage);
            helper.Write(mem, infohead.biXPelsPerMeter);
            helper.Write(mem, infohead.biYPelsPerMeter);
            helper.Write(mem, infohead.biClrUsed);
            helper.Write(mem, infohead.biClrImportant);

            var len_colors = calc_colors();
            if (len_colors > 0) for (var i = 0; i < len_colors; i++) helper.WriteColor(mem, colors[i].ToBitColor(32));

            img_data.Position = 0;
            helper.ReadToMemory(img_data, mem, infohead.biSizeImage);
            for (var i = and.Height - 1; i >= 0; i--) mem.Write(and.grid[i], 0, and.width_byte);

            mem.Flush();
            mem.Seek(0, SeekOrigin.Begin);
            return mem;
        }

        public TuimgPng ToPng()
        {
            var png = new TuimgPng(infohead.biWidth, infohead.biHeight);
            for (var y = 0; y < infohead.biHeight; y++)
            {
                png.brush_goto(0, y);
                brush_goto(0, y);
                for (var x = 0; x < infohead.biWidth; x++) png.WriteColor(helper.ReadColorBgr(img_data, (short)infohead.biBitCount));
            }
            return png;
        }
        public TuimgPng ToPngAndTransparent(TuimgColor transparent_color, int threshold = 0)
        {
            TuimgColor color;
            var png = new TuimgPng(infohead.biWidth, infohead.biHeight);
            if (threshold == 0)
            {
                for (var y = 0; y < infohead.biHeight; y++)
                {
                    png.brush_goto(0, y);
                    brush_goto(0, y);
                    for (var x = 0; x < infohead.biWidth; x++)
                    {
                        color = helper.ReadColorBgr(img_data, (short)infohead.biBitCount);
                        if (!color.Equal(transparent_color, infohead.biBitCount)) png.WriteColor(color);
                        else png.brush_goto_next();
                    }
                }
            }
            else
            {
                var a = (byte)(threshold / 0x17f);
                for (var y = 0; y < infohead.biHeight; y++)
                {
                    png.brush_goto(0, y);
                    brush_goto(0, y);
                    for (var x = 0; x < infohead.biWidth; x++)
                    {
                        color = helper.ReadColorBgr(img_data, (short)infohead.biBitCount);
                        if (!color.Equal(transparent_color, infohead.biBitCount))
                        {
                            if (!color.Similar(transparent_color, threshold)) png.WriteColor(color);
                            else png.WriteColor(color.ChangeAlpha(a));
                        }
                        else png.brush_goto_next();
                    }
                }
            }
            return png;
        }

        public void DrawGrid(TuimgGrid grid, TuimgColor color)
        {
            DrawGrid(0, 0, grid, color);
        }
        public void DrawGrid(int x, int y, TuimgGrid grid)
        {
            DrawGrid(x, y, grid, TuimgColor.Black);
        }
        public void DrawGrid(int x, int y, TuimgGrid grid, TuimgColor color)
        {
            var _color = color.ToBitColor(infohead.biBitCount);
            int i, j, _y,_x;
            for (i = 0; i < grid.Height; i++) for(j = 0, _y = i + y, _x=x; j < grid.Width; j++,_x++) if (grid.Read(j, i)) draw_point(_x, _y, _color);
        }
        public void DrawPoint(int x, int y) { DrawPoint(x, y, TuimgColor.Black); }
        public void DrawPoint(int x, int y, TuimgColor color)
        {
            brush_goto(x, y);
            helper.WriteColor(img_data, color.ToBitColor(infohead.biBitCount));
        }
        public void WriteColor(TuimgColor color)
        {
            helper.WriteColor(img_data, color.ToBitColor(infohead.biBitCount));
        }
        internal void draw_point(int x, int y, object color)
        {
            brush_goto(x, y);
            helper.WriteColor(img_data, color);
        }
        public void DrawSolidBox(int x, int y, int width, int height, TuimgColor color)
        {
            var _color = color.ToBitColor(infohead.biBitCount);
            for (var i = 0; i < height && (y + i) < infohead.biHeight; i++)
            {
                brush_goto(x, y + i);
                for (var j = 0; j < width && (j + x) < infohead.biWidth; j++) helper.WriteColor(img_data, _color);
            }
        }
        // 左上
        internal void brush_goto(int x, int y)
        {
            img_data.Position = (infohead.biHeight_minus1 - y) * biWidth_plus1 + x* onepoint_size;
        }
        internal void brush_goto(int x, int y, int byteix)
        {
            img_data.Position = (infohead.biHeight_minus1 - y) * biWidth_plus1 + x * onepoint_size + byteix;
        }
        internal void brush_goto_next()
        {
            img_data.Position += onepoint_size;
        }
        internal int width { get { if (infohead.biWidth > 0) return infohead.biWidth; return -infohead.biWidth; } }
        internal int height { get { if (infohead.biHeight> 0) return infohead.biHeight; return -infohead.biHeight; } }
        // 每行补位
        internal int calc_padding() { return (4 - (((width * infohead.biBitCount)>>3)&3))&3; }
        // 一个像素 几byte
        internal int calc_onepixel() { return (infohead.biBitCount>>3); }
        // 调色板数据大小
        internal int calc_colors() { if (infohead.biClrUsed != 0 && infohead.biClrImportant != 0) return (int)infohead.biClrImportant; return (int)(head.bfOffBits - 14 - (infohead.biSize)>>2); }
        internal uint calc_imgdata_size() { return (uint)(width * height * calc_onepixel() + height * calc_padding()); }
        internal uint calc_filesize() { return (uint)(head.bfOffBits + calc_imgdata_size()); }
        internal TuimgColor read_color(int ix) { return colors[ix]; }

        public void Dispose()
        {
            if (img_data != null) img_data.Dispose();
            img_data = null;
        }
        internal int read(int x, int y)
        {
            brush_goto(x, y);
            var v = 0;
            if (infohead.biBitCount == 32)
                v |= helper.Readbyte(img_data) << 24;
            if (infohead.biBitCount >= 24)
                v |= helper.Readbyte(img_data) << 16;
            if (infohead.biBitCount >= 16)
                v |= helper.Readbyte(img_data) << 8;
            if (infohead.biBitCount >= 8)
                v |= helper.Readbyte(img_data);

            return v;
        }
        internal int read(int x, int y, int ix)
        {
            brush_goto(x, y, ix);
            return helper.Readbyte(img_data);
        }
        public TuimgColor ReadColor()
        {
            if (has_color_palette) return colors[helper.Readbyte(img_data)];
            return helper.ReadColorBgr(img_data, (short)infohead.biBitCount);
        }
        //双线性插值
        public TuimgBmp Scale(float scale_value = 0.5f)
        {
            var h = (int)(height * scale_value);
            var w= (int)(width * scale_value);
            var bmp = new TuimgBmp(w, h, infohead.biBitCount);

            var scale_x = width / w;
            var scale_y = height / h;
            for (var y = 0; y< h; y++)
            {
                var src_y = (y + 0.5) * scale_x - 0.5;
                int y0 = helper.round_down(src_y);
                int y1 = helper.min(y0 + 1, height - 1);
                var v = src_y - y0;

                for (var x = 0; x < w; x++)
                {
                    var src_x = (x + 0.5) * scale_y - 0.5;
                    var x0 = helper.round_down(src_x);
                    var x1 = helper.min(x0 + 1, width - 1);
                    var u = src_x - x0;

                    for (var i = 0; i < onepoint_size; i++)
                    {
                        var p00 = read(x0, y0);
                        var p01 = read(x1, y0);
                        var p10 = read(x0, y1);
                        var p11 = read(x1, y1);
                        var temp0 = (1 - u) * p00 + u * p01;
                        var temp1 = (1 - u) * p10 + u * p11;
                        var p_ = (byte)((1 - v) * temp0 + v * temp1);
                        bmp.brush_goto(x, y, i);
                        helper.Write(bmp.img_data, p_);
                    }
                }
            }
            return bmp;
        }
    }
}
